#include "CProcessAttribute.h"
#include "CLogger.h"
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

pid_t CProcessAttribute::m_siParentPid = 0;
pid_t CProcessAttribute::m_siPid = 0;
size_t CProcessAttribute::m_snArgvNeedMem = 0;
size_t CProcessAttribute::m_snEnvNeedMem = 0;
int CProcessAttribute::m_snOSArgc = 0;
char** CProcessAttribute::m_slpOSArgv;
char* CProcessAttribute::m_slpEnvMem;
int CProcessAttribute::m_snDaemonized;
int CProcessAttribute::m_snProcessType;
int CProcessAttribute::m_snStopFlag;
sig_atomic_t CProcessAttribute::m_snReap;

void CProcessAttribute::init(int argc, char** argv, int nProcessType)
{
	m_siPid = getpid();
	m_siParentPid = getppid();

	// 统计argv所占内存
	for (size_t i = 0; i < argc; i++)
	{
		m_snArgvNeedMem += strlen(argv[i]) + 1;
	}

	// 统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记
	for (size_t i = 0; environ[i]; i++)
	{
		m_snEnvNeedMem += strlen(environ[i]) + 1;  //+1是因为末尾有\0,是占实际内存位置的，要算进来
	}

	m_snOSArgc = argc;
	m_slpOSArgv = argv;
	m_snProcessType = nProcessType;
	m_snReap = 0;
	m_snStopFlag = 0;

	//拷贝环境变量到新内存中来
	m_slpEnvMem = new char[m_snEnvNeedMem];
	memset(m_slpEnvMem, 0, m_snEnvNeedMem);

	char* lpTmp = m_slpEnvMem;
	for (size_t i = 0; environ[i]; i++)
	{
		size_t size = strlen(environ[i]) + 1;
		strcpy(lpTmp, environ[i]);
		environ[i] = lpTmp;
		lpTmp += size;
	}
}

//拷贝环境变量到新内存中来
void CProcessAttribute::initProcTitle()
{
	m_slpEnvMem = new char[m_snEnvNeedMem];
	memset(m_slpEnvMem, 0, m_snEnvNeedMem);

	char* lpTmp = m_slpEnvMem;
	for (size_t i = 0; environ[i]; i++)
	{
		size_t size = strlen(environ[i]) + 1;
		strcpy(lpTmp, environ[i]);
		environ[i] = lpTmp;
		lpTmp += size;
	}
}

void CProcessAttribute::setProcTitle(const char* lpTitle)
{
	//我们假设，所有的命令 行参数我们都不需要用到了，可以被随意覆盖了
	//注意：我们的标题长度，不会长到原始标题和原始环境变量都装不下，否则怕出问题，不处理
	//(1)计算标题长度
	size_t iTitleLen = strlen(lpTitle);
	//(2)计算总的原始的argv那块内存的总长度【包括各种参数】
	size_t iTotalLen = m_snArgvNeedMem + m_snEnvNeedMem;	//argv和environ内存总和
	if (iTotalLen < iTitleLen)
	{
		return;
	}

	//(3)设置后续的命令行参数为空，表示只有argv[]只有一个元素，防止后续argv被滥用，因为很多判断是用argv[] == NULL来做结束标记判断的
	m_slpOSArgv[1] = NULL;

	//(4)把标题弄进来，注意原来的命令行参数都会被覆盖掉，不要再使用这些命令行参数了，而且m_slpOSArgv[1]已经被设置为NULL了
	char* lpTmp = m_slpOSArgv[0];	//指向m_slpOSArgv
	strcpy(lpTmp, lpTitle);
	lpTmp += iTitleLen;	//跳过标题

	//(5)把剩余的argv和envrion所占的内存全部清0，否则会出现再ps的cmd列可能还会残余一些没有被覆盖的内容
	size_t iLeftLen = iTotalLen - iTitleLen;
	memset(lpTmp, 0, iLeftLen);
}

int CProcessAttribute::becomeDaemon()
{
	// (1)fork()一个子进程出来
	switch (fork())
	{
	case -1:
		// 创建子进程失败
		CLogger::logErrorCore(LOG_EMERG, errno, "becomeDaemon()中fork失败！");
		return -1;
	case 0:
		// 子进程
		break;
	default:
		// 父进程
		return 1;
	}
	m_siParentPid = m_siPid;	
	m_siPid = getpid();

	//(2)脱离终端，终端关闭。创建一个新的会话，调用进程成为会话领导进程，也成为进程组的组长
	if (setsid() == -1)
	{
		CLogger::logErrorCore(LOG_EMERG, errno, "becomeDaemon()中setsid失败！");
		return -1;
	}

	//(3)设置权限掩码为0，不要让他来限制文件权限，以免引起混乱
	umask(0);

	//(4)打开黑洞设备，以读写方式打开
	int fd = open("/dev/null", O_RDWR);
	if (fd == -1)
	{
		CLogger::logErrorCore(LOG_EMERG, errno, "becomeDaemon()中open(/dev/null)失败！");
		return -1;
	}
	if (dup2(fd, STDIN_FILENO) == -1)	//先关闭STDIN_FILENO[这是规矩，已经打开的描述符，动他之前，先close]，类似于指针指向null，让/dev/null成为标准输入
	{
		CLogger::logErrorCore(LOG_EMERG, errno, "becomeDaemon()中dup2(STDIN)失败！");
		return -1;
	}
	if (dup2(fd, STDOUT_FILENO) == -1)	//再关闭STDOUT_FILENO，类似于指针指向null，让/dev/null成为标准输出
	{
		CLogger::logErrorCore(LOG_EMERG, errno, "becomeDaemon()中dup2(STDOUT)失败");
		return -1;
	}
	if (fd > STDERR_FILENO)	//fd应该是3，这个应该成立
	{
		if (close(fd) == -1)	//释放资源这样这个文件描述符就可以被复用，不然这个数字【文件描述符】会被一直占着
		{
			CLogger::logErrorCore(LOG_EMERG, errno, "becomeDaemon()中close(fd)失败！");
			return -1;
		}
	}

	m_snDaemonized = 1;
	return 0;
}

void CProcessAttribute::deInit()
{
	if (m_slpEnvMem)
	{
		delete[] m_slpEnvMem;
		m_slpEnvMem = NULL;
	}
}
